﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		EditActions.cs
//
//	Author:		MJP
//
//	Date:		04/24/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Collections.ObjectModel;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

using CPXMenu;

namespace CPXMenuEditor
{
    /// <summary>
    /// Represents a single action taken by the user to edit the level,
    /// and that can be Undone/Redone.
    /// </summary>
    public interface IEditAction
    {
        /// <summary>
        /// Undoes the action
        /// </summary>
        void Undo();

        /// <summary>
        /// Redoes the action
        /// </summary>
        void Do();
    }

    /// <summary>
    /// Handles UndoRedo for properties (and public fields)
    /// </summary>
    public class PropertyEditAction : IEditAction
    {
        string propertyName;
        object[] oldValues;
        object[] propertyOwners;
        object[] newValues;

        public PropertyEditAction(object[] propertyOwners,
                                    object[] oldValues,
                                    object newValue,
                                    string propertyName)
        {
            Debug.Assert(oldValues.Length == propertyOwners.Length);

            this.oldValues = oldValues;
            this.propertyOwners = propertyOwners;
            this.propertyName = propertyName;
            this.newValues = new object[propertyOwners.Length];
            for (int i = 0; i < propertyOwners.Length; i++)
                newValues[i] = newValue;
        }

        public PropertyEditAction(object[] propertyOwners,
                                    object[] oldValues,
                                    object[] newValues,
                                    string propertyName)
        {
            Debug.Assert(oldValues.Length == propertyOwners.Length);

            this.oldValues = oldValues;
            this.propertyOwners = propertyOwners;
            this.propertyName = propertyName;
            this.newValues = newValues;
        }

        private void SetPropertyValue(object owner, string propertyName, object value)
        {
            Type ownerType = owner.GetType();
            // Try property first, then public field
            PropertyInfo property = ownerType.GetProperty(propertyName);
            if (property != null)
                property.SetValue(owner, value, null);
            else
            {
                FieldInfo field = ownerType.GetField(propertyName);
                if (field != null)
                    field.SetValue(owner, value);
                else
                    Debug.Assert(false);
            }
        }

        public void Undo()
        {
            for (int i = 0; i < propertyOwners.Length; i++)
                this.SetPropertyValue(propertyOwners[i], propertyName, oldValues[i]);
        }

        public void Do()
        {
            for (int i = 0; i < propertyOwners.Length; i++)
                this.SetPropertyValue(propertyOwners[i], propertyName, newValues[i]);
        }
    }

    /// <summary>
    /// Handles undo/redo for adding or removing a GameObject
    /// </summary>
    public class AddRemoveItemAction : IEditAction
    {
        ActionManager manager;
        MenuProject project;
        MenuItem[] items;
        MenuItem[] parents;
        bool added;

        public bool Added
        {
            get { return added; }
        }

        public MenuItem[] Items
        {
            get { return items; }
        }

        public AddRemoveItemAction(ActionManager manager, MenuItem[] items, MenuItem[] parents, bool added)
        {
            Debug.Assert(items.Length == parents.Length);

            this.manager = manager;
            this.project = manager.MenuProject;
            this.items = items;
            this.parents = parents;
            this.added = added;
        }

        /// <summary>
        /// Adds the items back to the project
        /// </summary>
        private void AddItems()
        {
            project.AddToProject(parents, items);
            manager.SelectItems(items, false);
        }

        /// <summary>
        /// Removes the items from the project
        /// </summary>
        private void RemoveItems()
        {
            project.RemoveFromProject(items);
        }

        public void Undo()
        {
            if (added)
                RemoveItems();
            else
                AddItems();
        }

        public void Do()
        {
            if (!added)
                RemoveItems();
            else
                AddItems();
        }
    }

    public class CompoundAction : IEditAction
    {
        IEditAction[] actions;

        public CompoundAction(IEditAction[] actions)
        {
            this.actions = actions;
        }

        public void Undo()
        {
            foreach (IEditAction action in actions)
                action.Undo();
        }

        public void Do()
        {
            foreach (IEditAction action in actions)
                action.Do();
        }
        
    }

    /// <summary>
    /// Handles undo/redo for adding or removing an ImportedContentItem
    /// </summary>
    public class AddRemoveImportedContentAction : IEditAction
    {
        ActionManager manager;
        MenuProject project;
        ImportedContentItem[] items;
        ImportedContentDirectory[] parents;
        bool added;

        public bool Added
        {
            get { return added; }
        }

        public ImportedContentItem[] Items
        {
            get { return items; }
        }

        public AddRemoveImportedContentAction(ActionManager manager, ImportedContentItem[] items, ImportedContentDirectory[] parents, bool added)
        {
            this.manager = manager;
            this.project = manager.MenuProject;
            this.items = items;
            this.added = added;
            this.parents = parents;

            Debug.Assert(items.Length == parents.Length);
        }

        /// <summary>
        /// Adds the items back to the project
        /// </summary>
        private void AddItems()
        {
            for (int i = 0; i < items.Length; i++)
                project.AddImportedContent(items[i], parents[i]);            
        }

        /// <summary>
        /// Removes the items from the project
        /// </summary>
        private void RemoveItems()
        {
            foreach (ImportedContentItem item in items)
                project.RemoveImportedContent(item);   
        }

        public void Undo()
        {
            if (added)
                RemoveItems();
            else
                AddItems();
        }

        public void Do()
        {
            if (!added)
                RemoveItems();
            else
                AddItems();
        }
    }

    /// <summary>
    /// Handles undo/redo for adding or removing an ImportedContentItem
    /// </summary>
    public class AddRemoveImportedContentDirectoryAction : IEditAction
    {
        ActionManager manager;
        MenuProject project;
        ImportedContentDirectory[] items;
        ImportedContentDirectory[] parents;
        bool added;

        public bool Added
        {
            get { return added; }
        }

        public ImportedContentDirectory[] Items
        {
            get { return items; }
        }

        public AddRemoveImportedContentDirectoryAction(ActionManager manager, ImportedContentDirectory[] items, ImportedContentDirectory[] parents, bool added)
        {
            this.manager = manager;
            this.project = manager.MenuProject;
            this.items = items;
            this.added = added;
            this.parents = parents;

            Debug.Assert(items.Length == parents.Length);
        }

        /// <summary>
        /// Adds the items back to the project
        /// </summary>
        private void AddItems()
        {
            for(int i = 0; i < items.Length; i++)
                project.AddImportedContentDirectory(items[i], parents[i]);
        }

        /// <summary>
        /// Removes the items from the project
        /// </summary>
        private void RemoveItems()
        {
            foreach (ImportedContentDirectory item in items)
                project.RemoveImportedContentDirectory(item);
        }

        public void Undo()
        {
            if (added)
                RemoveItems();
            else
                AddItems();
        }

        public void Do()
        {
            if (!added)
                RemoveItems();
            else
                AddItems();
        }
    }
}
